import MemoryCache from "./MemoryCache";

export default class CommonUtil {

    /**
     * 延时多长时间触发.
     * @param {number} timeSecond
     * @param scheduleTarget  如果传递一个可以用schedule的对象,则优先用schedule.
     * @returns {Promise<number>}
     */
    public static async delay(timeSecond: number, scheduleTarget: any = null): Promise<number> {
        if (scheduleTarget) {
            return new Promise<number>((resolve) => {
                cc.director.getScheduler().schedule(() => {
                    resolve();
                }, scheduleTarget, 0, 0, timeSecond, false);
            });
        } else {
            return new Promise<number>((resolve) => {
                const id = setTimeout(
                    function () {
                        resolve(id);
                    }.bind(this),
                    timeSecond * 1000);
            });
        }
    }

    // 获取当前天数.
    public static getDays(ts?: number): number {
        return Math.floor((ts || cc.sys.now()) / (24 * 360000));
    }

    /**
     * 定时schedule.
     * @param {number} interval
     * @param {number} times
     * @param {Function} callback
     */
    public static schedule(interval: number, times: number, callback: Function): number {
        let count = 0;
        const timer: number = setInterval(
            function (dt) {
                callback(dt);
                if (times == cc.macro.REPEAT_FOREVER) {
                } else if (++count >= times) {
                    clearInterval(timer);
                }
            },
            interval * 1000);
        return timer;
    }

    public static unschedule(timer: number) {
        clearInterval(timer);
    }

    /**
     * 下载/请求远程资源. 通用于微信和其他平台.
     * @param {string} pathUrl
     * @param data
     * @param header
     * @returns {Promise<string>}
     */
    public static download(pathUrl: string, data?: any, header?: any): Promise<string> {
        return new Promise<string>(((resolve, reject) => {
            const xhr = cc.loader.getXMLHttpRequest();
            xhr.open("GET", pathUrl, true);
            xhr.responseType = "text";
            if (header) {
                for (const prop in header) {
                    if (header.hasOwnProperty(prop)) {
                        xhr.setRequestHeader(prop, header[prop]);
                    }
                }
            }
            xhr.onload = () => {
                resolve(xhr.response);
            };
            xhr.onerror = (ret) => {
                reject(ret);
            };
            xhr.send(data);
        }));
    }

    // 随机int 值,不包含max
    public static randomInt(min: number = 0, _maxExclude?: number): number {
        const maxExclude = _maxExclude || Math.pow(2, 58);
        if (min >= _maxExclude) {
            return min;
        }
        let c = Math.floor(Math.random() * (maxExclude - min)) + min;
        if (c >= maxExclude) {
            c = Math.floor(maxExclude - 1);
        }
        return c;
    }

    // 用于随机一个数组索引.
    public static randomIndex(len: number): number {
        return this.randomInt(0, len);
    }

    // 随机key. 给定一个map, 随机获取一个key 值.
    public static randomKey(obj: any): string {
        if (!obj) {
            return null;
        }
        let keys = Object.keys(obj);
        if (keys && keys.length > 0) {
            return keys[this.randomInt(0, keys.length)];
        } else {
            return null;
        }
    }

    /**
     * 带权重的选择map的key
     * @param weight
     */
    public static randomWeight(weight: { [key: string]: number }): string {
        let totalWeight = 0;
        let startWeight = 0;
        for (let w in weight) {
            if (weight.hasOwnProperty(w)) {
                totalWeight += weight[w]
            }
        }
        const rand = Math.random() * totalWeight;
        for (let w in weight) {
            if (weight.hasOwnProperty(w)) {
                if (rand >= startWeight && rand <= startWeight + weight[w]) {
                    return w;
                }
                startWeight += weight[w];
            }
        }
        return this.randomKey(weight);
    }

    // 格式化数字,不足10,加0
    public static formatNumber(num: number): string {
        return num >= 10 ? `${num}` : `0${num}`;
    }

    // 异步动画执行API. 方便链式调用.
    public static async runAction(node: cc.Node | cc.Component, action: cc.FiniteTimeAction): Promise<any> {
        if (!action || !node) {
            console.error("runAction param is null:", node, action);
            return;
        }
        return new Promise<any>((resolve) => {
            const nd = node instanceof cc.Component ? node.node : node;
            nd.stopAllActions();
            nd.runAction(cc.sequence(action, cc.callFunc(() => {
                resolve();
            })));
        });
    }

    public static loadRes<T extends cc.Asset>(resource: string, type?: any): Promise<T> {
        return new Promise((resolve, reject) => {
            if (type) {
                cc.loader.loadRes(resource, type, (err: Error, res: any) => {
                    if (err) {
                        return reject(err);
                    } else {
                        return resolve(res);
                    }
                });
            } else {
                cc.loader.loadRes(resource, (err: Error, res: any) => {
                    if (err) {
                        return reject(err);
                    } else {
                        return resolve(res);
                    }
                });
            }
        });
    }

    // 加载失败也不报异常.返回空值.
    public static loadResNoError<T extends cc.Asset>(resource: string, type?: any): Promise<T> {
        return new Promise((resolve,) => {
            if (type) {
                cc.loader.loadRes(resource, type, (err: Error, res: any) => {
                    if (err) {
                        cc.error("加载资源异常:", resource, err);
                        return resolve(null)
                    } else {
                        return resolve(res)
                    }
                })
            } else {
                cc.loader.loadRes(resource, (err: Error, res: any) => {
                    if (err) {
                        cc.error("加载资源异常2:", resource, err);
                        return resolve(null)
                    } else {
                        return resolve(res)
                    }
                })
            }
        });
    }

    public static loadDragonBones(resource: string, display: dragonBones.ArmatureDisplay): Promise<any> {
        const assets = [resource + "_ske.json", resource + "_tex.json"];
        return new Promise(resolve => {
            cc.loader.loadResArray(assets,
                [dragonBones.DragonBonesAsset, dragonBones.DragonBonesAtlasAsset, cc.Texture2D],
                (err: Error, res: any) => {
                    if (err) {
                        cc.error("加载资源异常:", resource, err);
                    } else {
                        display.dragonAsset = res[0];
                        display.dragonAtlasAsset = res[1];
                    }
                    resolve();
                })
        });
    }

    /**
     * 加载图片资源.
     * @param {string} path
     * @param cached
     * @returns {Promise<cc.SpriteFrame>}
     */
    public static async loadImage(path: string, cached: boolean = true): Promise<cc.SpriteFrame> {
        if (!path || path.length <= 0) {
            console.warn("图片加载路径为空,请检查.");
            return Promise.resolve(new cc.SpriteFrame());
        }
        const bigPath = path;
        let t = MemoryCache.instance.get(bigPath, null);
        if (!t) {
            t = await CommonUtil.loadResNoError<any>(bigPath);
        }

        let ret: any = t;
        if (t instanceof cc.Texture2D) {
            ret = new cc.SpriteFrame(t);
            // cc.log(path, "cc.Texture2D")
        } else if (t instanceof cc.SpriteFrame) {
            // cc.log(path, "cc.SpriteFrame")
        } else {
            ret = new cc.SpriteFrame();
            // cc.log(path, "null", t);
        }
        if (t && cached) {
            MemoryCache.instance.put(bigPath, ret);
        }
        return Promise.resolve(ret);
    }

    /**
     * 动态更新进度条
     * @param {cc.ProgressBar} progressbar
     * @param _progress
     */
    public static animateProgress(progressbar: cc.ProgressBar, _progress: number) {
        const oldProgress = progressbar.progress;
        if (oldProgress == _progress) {
            return;
        }
        let progress = _progress;
        if (progress > 1) {
            progress = 1;
        }
        if (progress < 0) {
            progress = 0;
        }
        const delta = progress - oldProgress;

        const callback = cc.callFunc(function () {
            // 可加可减
            this.progress += (delta > 0 ? 0.01 : -0.01);

            if (this.progress < 0) {
                this.progress = 0;
            }
            if (this.progress > 1) {
                this.progress = 1;
            }
        }.bind(progressbar));
        const action = cc.repeat(cc.sequence(cc.delayTime(0.01), callback), Math.ceil(Math.abs(delta / 0.01) + 5));
        action.setTag(0);
        progressbar.node.stopActionByTag(0);
        progressbar.node.runAction(action);
    }

    public static post(_url: string, _header?: any, _body?: any, method: string = "POST"): Promise<any> {
        if (method && "GET" === method.toUpperCase()) {
            return CommonUtil.get(_url, _header, _body);
        }
        return this.postWithXmlHttpRequest(_url, _header, _body)
    }

    public static get(_url: string, _header?: any, _body?: any): Promise<any> {
        return new Promise<any>((resolve, reject) => {
            let url = _url;
            let body = _body;
            let header = _header || {};

            // get 请求,只能通过拼接参数到URL中来请求.
            if (body) {
                let params = "";
                if (url.lastIndexOf("?") < 0) {
                    params += "?";
                } else if (url.charAt(url.length - 1) != "&") {
                    params += "&";
                }
                for (const prop in body) {
                    if (body.hasOwnProperty(prop)) {
                        params += `${prop}=${body[prop]} &`;
                    }
                }
                params += `_ts=${Date.now()}`;
                url += params;
                body = null;
            }
            fetch(url, {
                method: "GET",
                headers: header
            }).then((response) => {
                return response.json();
            }).then((json) => {
                resolve(json);
            }).catch((err) => {
                reject(err);
            })
        });
    }

    public static postWithXmlHttpRequest(_url: string, _header?: any, _body?: any,): Promise<any> {
        return new Promise<any>((resolve, reject) => {
            let body = _body || {};
            let header = _header || {};
            let form = "";
            for (let i in body) {
                if (body.hasOwnProperty(i)) {
                    form += `${i}=${encodeURIComponent(body[i])}&`
                }
            }
            header["Content-Type"] = "application/x-www-form-urlencoded; charset=utf-8";
            // header["Accept-Encoding"] = "gzip,deflate"
            const xhr = new XMLHttpRequest();
            xhr.timeout = 15000;
            xhr.responseType = "text";

            xhr.ontimeout = function () {
                cc.log("=========请求超时!!");
                reject()
            };
            xhr.onerror = function (e) {
                reject(e)
            };
            xhr.open('POST', _url);
            for (let k in header) {
                if (header.hasOwnProperty(k)) {
                    xhr.setRequestHeader(k, header[k])
                }
            }
            const data = form;
            xhr.send(data);
            xhr.onreadystatechange = function () {
                if (xhr.readyState == 4 && xhr.status == 200) {
                    const response = xhr.response;
                    resolve(JSON.parse(response))
                }
            }
        });
    }

    // 从obj中随机出size个元素key. 且不能重复。
    public static randomKeys(obj: any, size: number = 1, enableDup: boolean = false): any[] {
        let keys = Object.keys(obj);
        if (keys.length <= 0) return [];

        if (size <= 0) size = 1;
        else if (size > keys.length)
            size = keys.length;
        const ret = [];
        for (let i = 0; i < size;) {
            let key = keys[this.randomInt(0, keys.length)];
            if (!enableDup && ret.indexOf(key) >= 0) {
                continue;
            }
            ret.push(key);
            i++;
        }
        return ret;
    }

    // 随机出若干数字，最大值为until.最小值为0,用于随机挑出一个数组的若干索引值。
    public static randomNumbers(until: number, size: number = 1, enableDup: boolean = false): number[] {
        if (until < 0) return [];
        if (size <= 0) size = 1;
        else if (size > until)
            size = until;
        const ret = [];
        for (let i = 0; i < size;) {
            let key = this.randomInt(0, until);
            if (!enableDup && ret.indexOf(key) >= 0) {
                continue;
            }
            ret.push(key);
            i++;
        }
        return ret;
    }

    // 随机产出若干数字，数字总和为一给定值。用于若干对象分配一定的资源问题。
    public static randomSum(sum: number, size: number = 1): number[] {
        if (sum < 1) return [];
        if (size <= 0) size = 1;
        let totalWeight = 0;
        let realSum = 0;
        const weights = [];
        const rets = [];
        for (let i = 0; i < size; i++) {
            const r = CommonUtil.randomInt(100, sum * 100);
            weights.push(r);
            totalWeight += r;
        }
        for (let i = 0; i < size; i++) {
            let d = Math.floor(weights[i] * sum / totalWeight);
            if (d <= 0) d = 1;
            realSum += d;
            rets.push(d);
        }
        // 由于小数精度截断，实际总数比预计少了一点，需要随机做出补贴。
        const dx = realSum - sum;
        if (dx < 0) {
            rets[CommonUtil.randomIndex(size)] -= dx;
        } else if (dx > 0) {
            //如果超出了，则要找个比较大的减去。
            for (let i = 0; i < rets.length; i++) {
                if (rets[i] > dx) {
                    rets[i] -= dx;
                    break;
                }
            }
        }
        return rets;
    }

    public static radius(angle: number): number {
        return Math.PI * angle / 180;
    }

    public static angle(radius: number): number {
        return radius * 180 / Math.PI;
    }
};
